/********************************************************************************
* Description: 
* Author: hsqiang
* Create Date: 2023-08-06
********************************************************************************/
#ifndef OBJECT_POOL_HPP_
#define OBJECT_POOL_HPP_

#include <vector>
#include <memory>
#include <mutex>
#include <functional>
#include <sstream>
#include <iostream>

namespace objpool
{
    template<typename Obj>
    class ObjectPool;

    template<typename Obj>
    class ObjectPool : std::enable_shared_from_this<ObjectPool<Obj>>
    {
        using ObjPoolPtr = typename std::shared_ptr<ObjectPool<Obj>>;
        using ObjPtr = typename std::shared_ptr<Obj>;
        using Creator = typename std::function<Obj*()>;
        using ObjList = typename std::vector<ObjPtr>;
    public:
        ObjectPool(size_t initCnt, size_t mCnt, bool allowEx, Creator&& crtr)
            : totalCnt(initCnt), maxCnt(mCnt), creator(std::move(crtr)), allowExpand(allowEx)
        {
            while (initCnt--)
            {
                objList.emplace_back(CreateObj());
            }
        }
        ~ObjectPool() = default;

        void Finish()
        {
            std::lock_guard<std::mutex> lck(mtx);
            finish = true;
        }

        ObjPtr BorrowOne()
        {
            std::lock_guard<std::mutex> lck(mtx);
            if (finish)
            {
                return nullptr;
            }
            if (!objList.empty())
            {
                auto obj = objList.back();
                objList.pop_back();
                ++usedCnt;
                return obj;
            }
            if (allowExpand && totalCnt < maxCnt)
            {
                auto obj = CreateObj();
                ++usedCnt;
                ++totalCnt;
                return obj;
            }
            return nullptr;
        }

        ObjList BorrowMore(int need)
        {
            ObjList lst;
            std::lock_guard<std::mutex> lck(mtx);
            if (finish)
            {
                return lst;
            }
            int freeCnt = objList.size();
            if (need > freeCnt)
            {
                lst.insert(lst.end(), objList.begin(), objList.end());
                objList.clear();
                usedCnt += freeCnt;
                if (!allowExpand) return lst;
                int curCnt = need - freeCnt;
                int allowCnt = maxCnt - totalCnt;
                curCnt = curCnt > allowCnt ? allowCnt : curCnt;
                usedCnt += curCnt;
                totalCnt += curCnt;
                while (curCnt--)
                {
                    lst.emplace_back(CreateObj());
                }
                return lst;
            }
            int offset = freeCnt - need;
            lst.insert(lst.end(), objList.begin() + offset, objList.end());
            objList.erase(objList.begin() + offset, objList.end());
            usedCnt += need;
            return lst;
        }

        void UseInfo()
        {
            std::lock_guard<std::mutex> lck(mtx);
            std::cout << "[ObjectPool<" << typeid(Obj).name() << ">] "
                << ", total=" << totalCnt
                << ", max=" << maxCnt
                << ", used=" << usedCnt
                << ", free=" << objList.size()
                << ", allowEx=" << allowExpand
                << std::endl;
        }
    private:
        ObjPtr CreateObj()
        {
            return ObjPtr(creator(), [this](Obj* o) { this->ReturnOne(o); });
        }

        void ReturnOne(Obj* obj)
        {
            std::lock_guard<std::mutex> lck(mtx);
            if (!finish)
            {
                objList.push_back(ObjPtr(obj, [this](Obj* o) { this->ReturnOne(o); }));
                --usedCnt;
            }
            else
            {
                ObjPtr ptr(obj);
            }
        }

        bool finish { false };
        ObjList objList;      // all object stored in objList must have deleter, and all borrowned object must have deleter too
        size_t usedCnt { 0 };
        size_t totalCnt { 0 };
        size_t maxCnt { 0 };
        std::mutex mtx;
        Creator creator;
        bool allowExpand { true };
    };
}

#endif